package openjdk.reflection;

import java.lang.reflect.*;
import java.util.Arrays;
import java.util.List;

/**
 * Generic Array Type
 *
 * @param <T>
 */
public class GetGenericTypeExample5<T> {
    private T[] ts;
    private List<T[]> tsList;

    public static void main(String... args) throws NoSuchFieldException {
        System.out.println("Example: T[] ts;");
        Field field = GetGenericTypeExample5.class.getDeclaredField("ts");
        Type genericType = field.getGenericType();
        printType(genericType, 1);

        System.out.println("Example: List<T[]> tsList;");
        Field field2 = GetGenericTypeExample5.class.getDeclaredField("tsList");
        Type genericType2 = field2.getGenericType();
        printType(genericType2, 1);
    }

    private static void printType(Type type, int level) {
        if (type == null) {
            print(level, "The Type is null");
            return;
        }
        print(level, "Type class=" + type.getClass());
        if (type.getClass() != Class.class) {
            print(level, "Type class implementing interfaces=" +
                    Arrays.toString(type.getClass().getInterfaces()));
        }
        print(level, "Type name= " + type.getTypeName());
        printTypeVariable(type, level);
        printParameterizedType(type, level);
        printGenericArrayType(type, level);
    }

    private static void printGenericArrayType(Type type, int level) {
        if (type instanceof GenericArrayType) {
            print(level, "Casting to GenericArrayType");
            GenericArrayType genericArrayType = (GenericArrayType) type;
            print(level, "Using GenericArrayType#getGenericComponentType()");
            Type genericComponentType = genericArrayType.getGenericComponentType();
            printType(genericComponentType, level + 1);
        }
    }

    private static void printTypeVariable(Type type, int level) {
        if (type instanceof TypeVariable) {
            print(level, "Casting to TypeVariable");
            TypeVariable typeVariable = (TypeVariable) type;
            print(level, "TypeVariable#getName()= " + typeVariable.getName());
            GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
            print(level, "TypeVariable#getGenericDeclaration()= " + genericDeclaration);
            printGenericDeclaration(level, genericDeclaration);
        }
    }

    private static void printGenericDeclaration(int level, GenericDeclaration genericDeclaration) {
        print(level, "GenericDeclaration#getTypeParameters()= " +
                Arrays.toString(genericDeclaration.getTypeParameters()));
    }

    private static void printParameterizedType(Type type, int level) {
        if (type instanceof ParameterizedType) {
            print(level, "Casting to ParameterizedType");
            ParameterizedType pt = (ParameterizedType) type;
            print(level, "Using ParameterizedType#getOwnerType()");
            Type ownerType = pt.getOwnerType();
            printType(ownerType, level + 1);

            print(level, "Using ParameterizedType#getRawType()");
            Type rawType = pt.getRawType();
            printType(rawType, level + 1);

            print(level, "Using ParameterizedType#getActualTypeArguments()");
            for (Type actualType : pt.getActualTypeArguments()) {
                print(level, "ActualTypeArgument loop");
                printType(actualType, level + 1);
            }
        }
    }

    private static void print(int level, String string) {
        System.out.printf("%" + (level * 4 - 3) + "s\u00A6- %s%n", "", string);
    }
}